package fun.qu_an.minecraft.mini_games_helper.client.mixin.iris;

import fun.qu_an.minecraft.mini_games_helper.client.handle.ForceOptionHelperSodium;
import net.irisshaders.iris.Iris;
import net.irisshaders.iris.IrisLogging;
import net.irisshaders.iris.gl.shader.StandardMacros;
import net.irisshaders.iris.shaderpack.ShaderPack;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerInteractionManager;
import net.minecraft.client.resource.server.ServerResourcePackLoader;
import net.minecraft.client.resource.server.ServerResourcePackManager;
import net.minecraft.world.GameMode;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.file.*;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

@Mixin(Iris.class)
public abstract class IrisMixin {
	@Shadow(remap = false)
	public static boolean isValidShaderpack(Path pack) {
		return false;
	}

	@Shadow(remap = false)
	@Final
	public static IrisLogging logger;

	@Shadow(remap = false)
	private static Optional<Path> loadExternalZipShaderpack(Path shaderpackPath) throws IOException {
		return Optional.empty();
	}

	@Shadow(remap = false)
	private static Optional<Properties> tryReadConfigProperties(Path path) {
		return Optional.empty();
	}

	@Shadow(remap = false)
	@Final
	private static Map<String, String> shaderPackOptionQueue;

	@Shadow(remap = false)
	public static void clearShaderPackOptionQueue() {
	}

	@Shadow(remap = false)
	private static boolean resetShaderPackOptions;
	@Shadow(remap = false)
	private static ShaderPack currentPack;

	@Shadow(remap = false)
	private static void tryUpdateConfigPropertiesFile(Path path, Properties properties) {
	}

	@Shadow(remap = false)
	private static boolean fallback;
	@Shadow(remap = false)
	private static String currentPackName;

	@Shadow(remap = false)
	private static void setShadersDisabled() {
	}

	@Unique
	private static final byte[] mgh$zipHeader = {0x50, 0x4B, 0x03, 0x04};

	@Inject(method = "loadShaderpack()V",
		remap = false, at = @At(value = "HEAD"), cancellable = true)
	private static void onLoadShaderPack(CallbackInfo ci) {
		// TODO load shader after load server rp
		logger.info("mixed!!!!!");

		MinecraftClient client = MinecraftClient.getInstance();

		ClientPlayerInteractionManager interactionManager = client.interactionManager;
		if (interactionManager == null) {
			logger.info("gamemode not init!");
			return;
		}

		GameMode gameMode = interactionManager.getCurrentGameMode();
		if (!ForceOptionHelperSodium.shouldTryServerRP(gameMode)) {
			logger.info("current game mode has no force sp");
			return;
		}

		logger.info("loading server rp as shader...");

		ServerResourcePackLoader serverRPLoader = client.getServerResourcePackProvider();
		ServerResourcePackManager serverRPManager = serverRPLoader.manager;
		List<ServerResourcePackManager.PackEntry> packs = serverRPManager.packs;
		Properties shaderPackCfg = new Properties();

		// the server shader should be also a valid resource pack
		Optional<Path> shaderPackRoot = packs.stream().filter(packEntry -> packEntry.path != null)
			.map(e -> e.path)
			.filter(path -> {
				logger.info("checking " + path);
				try (ZipFile zipFile = new ZipFile(path.toFile())) {
					boolean isShaderPack = false;

					// server pack should be zip
					for (Enumeration<? extends ZipEntry> entries = zipFile.entries(); entries.hasMoreElements(); ) {
						ZipEntry zipEntry = entries.nextElement();
						String name = zipEntry.getName();

						int i;
						if (!name.startsWith("shaders/")
							&& ((i = name.indexOf("/")) < 0
								|| !name.substring(i).contains("shaders/"))) {
							continue;
						}

						if (!isShaderPack) {
							isShaderPack = true;
						}

						if (!name.endsWith(".shaderpackcfg")) {
							continue;
						}

						try (InputStream inputStream = zipFile.getInputStream(zipEntry)) {
							shaderPackCfg.load(inputStream);
						} catch (IOException e) {
							return false;
						}
					}
					return isShaderPack;
				} catch (IOException e) {
					return false;
				}
			})
			.findFirst();

		if (shaderPackRoot.isEmpty()) {
			logger.info("server rp is not shader, skipped");
			return;
		}

		//noinspection unchecked
		if (!mgh$loadExternalShaderPack(shaderPackRoot.get(), (Map<String, String>) (Object) shaderPackCfg)) {
			setShadersDisabled();
			fallback = true;
			logger.info("load server rp as shader failed!");
		} else {
			fallback = false;
			logger.info("loaded server rp as shader!");
		}

		// loaded by force, cancel!
		ci.cancel();
	}

	@Unique
	private static boolean mgh$loadExternalShaderPack(Path shaderPackRoot, Map<String, String> shaderPackConfig) {
		String name = String.valueOf(shaderPackRoot.getFileName());

		if (!isValidShaderpack(shaderPackRoot)) {
			logger.error("Pack \"{}\" is not valid! Can't load it.", name);
			return false;
		}

		Path shaderPackPath;
		if (Files.isDirectory(shaderPackRoot) || !mgh$checkZipStr(shaderPackRoot)) {
			if (!Files.exists(shaderPackRoot)) {
				logger.error("Failed to load the shaderpack \"{}\" because it does not exist!", name);
				return false;
			}

			shaderPackPath = shaderPackRoot.resolve("shaders");
		} else {
			Optional<Path> optionalPath;
			try {
				optionalPath = loadExternalZipShaderpack(shaderPackRoot);
			} catch (NoSuchFileException | FileSystemNotFoundException var8) {
				logger.error("Failed to load the shaderpack \"{}\" because it does not exist in your shaderpacks folder!", name);
				return false;
			} catch (ZipException var9) {
				logger.error("The shaderpack \"{}\" appears to be corrupted, please try downloading it again!", name);
				return false;
			} catch (IOException var10) {
				logger.error("Failed to load the shaderpack \"{}\"!", name);
				logger.error("", var10);
				return false;
			}

			if (optionalPath.isEmpty()) {
				logger.error("Could not load the shaderpack \"{}\" because it appears to lack a \"shadersssssssssssssss\" directory", name);
				return false;
			}

			shaderPackPath = optionalPath.get();
		}

		if (!Files.exists(shaderPackPath)) {
			logger.error("Could not load the shaderpack \"{}\" because it appears to lack aaaaaaaaaaaaaaa \"shaders\" directory", name);
			return false;
		}

		try {
			currentPack = new ShaderPack(shaderPackPath, shaderPackConfig, StandardMacros.createStandardEnvironmentDefines());
		} catch (Exception var7) {
			logger.error("Failed to load the shaderpack \"{}\"!", name);
			logger.error("", var7);
			return false;
		}

		currentPackName = name;
		logger.info("Using shaderpack: " + name);
		return true;
	}

	@Redirect(method = "loadExternalShaderpack(Ljava/lang/String;)Z",
		remap = false, at = @At(value = "INVOKE", target = "Ljava/nio/file/Path;toString()Ljava/lang/String;"))
	private static String onLoadExternalShaderPackCheckZip(Path instance) {
		return mgh$checkZipStr(instance) ? ".zip" : "";
	}

	@Redirect(method = "isValidShaderpack(Ljava/nio/file/Path;)Z",
		remap = false, at = @At(value = "INVOKE", target = "Ljava/nio/file/Path;toString()Ljava/lang/String;"))
	private static String onIsValidShaderpackCheckZip(Path instance) {
		return mgh$checkZipStr(instance) ? ".zip" : "";
	}

	@Unique
	private static boolean mgh$checkZipStr(Path instance) {
		try (RandomAccessFile raf = new RandomAccessFile(instance.toFile(), "r")) {
			byte[] header = new byte[4];
			raf.read(header);
			return Arrays.equals(header, mgh$zipHeader);
		} catch (IOException e) {
			return instance.toString().endsWith(".zip");
		}
	}
}
